#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import os
import types
import logging
from typing import Dict, List
from unittest import result
import aiohttp_jinja2
import jinja2
import aiohttp_cors
import asyncio, inspect

from . import settings, filters, middlewares, db, corelib
from .logger import set_logger
from aiohttp import web, web_routedef, web_runner
from importlib import import_module

BASE_DIR = os.path.abspath(os.path.dirname(__file__))


def init_jinja(app):
    filters_ = {item: getattr(filters, item) for item in dir(
        filters) if isinstance(getattr(filters, item), types.FunctionType)}
    filters_local_ = {}
    if os.path.exists('filters.py'):
        filters_local = __import__('filters')
        filters_local_ = {item: getattr(filters_local, item) for item in dir(
            filters_local) if isinstance(getattr(filters_local, item), types.FunctionType)}
    filters_ = dict(filters_, **filters_local_)
    logging.info('load filters: %s' % filters_)
    # filters = dict(status_filter=status_filter, size_filter=size_filter, datetime_filter=datetime_filter, duration_filter=duration_filter, range_slice=range_slice)
    path = '%s/templates' % BASE_DIR
    loader = jinja2.FileSystemLoader([path, 'templates'])
    env = jinja2.Environment(loader=loader)
    env.filters = dict(env.filters, **filters_)
    app['__templating__'] = env
    aiohttp_jinja2.setup(app, loader=loader, filters=filters_)


'''
获取指定模块的属性
'''


def get_module(module_name, name):
    mod = None
    n = module_name.rfind('.')
    if n == (-1):
        mod = __import__(module_name, globals(), locals())
    elif n == 0:
        name_ = module_name[n+1:]
        mod = getattr(__import__(
            os.path.basename(os.path.dirname(__file__)), globals(), locals(), [name_]), name_)
    else:
        name_ = module_name[n+1:]
        mod = getattr(__import__(
            module_name[:n], globals(), locals(), [name_]), name_)
    return getattr(mod, name, None)


def load_view(app, path, views=[]):
    '''
    扫描以view_开头的模块
    '''
    if not os.path.exists(path):
        return
    if path not in sys.path:
        sys.path.append(path)
    modules = [item if '.' not in item else item.split('.', 1)[0] for item in os.listdir(path) if item.startswith('view_')]
    viewMap = {}
    for module_name in modules:
        # module_name = '.%s' % module_name
        route = get_module(module_name, 'routes')
        if not route:
            continue
        prefix = get_module(module_name, 'module_name')
        if prefix and prefix not in views:
            views.append(prefix)
        viewMap[module_name] = []
        for item in route:
            # 只打印RouteDef,避免StaticDef报错
            if isinstance(item, web_routedef.RouteDef):
                viewMap[module_name].append({'method': item.method, 'path': item.path, 'fun_name': item.handler.__name__})
        app.add_routes(route)
    if app.get('mapping', None) is None:
        app['mapping'] = {'CWD': {}}
    app['mapping']['CWD'] = viewMap

def add_routes(app):
    views = []
    # load_view(app, os.path.dirname(__file__), views)
    # load_view(app, 'src/web', views)
    load_view(app, 'views')
    if '/' not in [resource._path if hasattr(resource, '_path') else '' for resource in app.router.resources()]:
        app.router.add_get('/', index, name='index_inner')
    app['view_commons'] = {'views': views}


@aiohttp_jinja2.template('index.html')
async def index(request):
    pass


def add_static_routes(app):
    if os.path.exists('static'):
        app.router.add_static('/static/', path='static', name='static')
    static_path = app.__config__.get('static', {})
    for key, path in static_path.items():
        if os.path.exists(path):
            name = key[1:].replace('/', '_') if key.startswith('/') else key.replace('/', '_')
            app.router.add_static(key, path=path, name=name)


def setup_middlewares(app):
    # middlewares
    add_middlewares(app, middlewares, 'INNER')
    if os.path.exists('middlewares.py'):
        middlewares_local = import_module('middlewares')
        add_middlewares(app, middlewares_local, 'CWD')

def add_middlewares(app, middlewares, module_):
    for item in dir(middlewares):
        mw = getattr(middlewares, item)
        if isinstance(mw, types.FunctionType) and mw != web.middleware:
            mw.module_ = module_
            app.middlewares.append(mw)

def background_tasks(app):
    if not os.path.exists('background_tasks.py'): return
    tasks = import_module('background_tasks')
    for item in dir(tasks):
        task = getattr(tasks, item)
        if isinstance(task, types.FunctionType) and getattr(task, 'task', False):
            app.cleanup_ctx.append(task)

def cors(app):
    # Configure default CORS settings.
    cors = aiohttp_cors.setup(app, defaults={
        "*": aiohttp_cors.ResourceOptions(
            allow_credentials=True,
            expose_headers="*",
            allow_headers="*",
        )
    })

    # Configure CORS on all routes.
    for route in list(app.router.routes()):
        cors.add(route)


async def app_factory_local():
    set_logger()
    # db.init_db('dev')
    app = web.Application()
    init_jinja(app)
    add_routes(app)
    add_static_routes(app)
    setup_middlewares(app)
    background_tasks(app)
    cors(app)
    printResource(app)
    return app


def load_filter_template(abs_path):
    name = os.path.basename(abs_path)
    filters_ = {}
    filters_file = os.path.join(abs_path, 'filters.py')
    if os.path.exists(filters_file):
        filters_local = import_module(f'plugins.{name}.filters')
        filters_ = {item: set_module(filters_local, item, name) for item in dir(
            filters_local) if isinstance(getattr(filters_local, item), types.FunctionType)}
    template_path = os.path.join(abs_path, 'templates')
    if os.path.exists(template_path):
        return ([template_path], filters_)
    return ([], filters_)


def load_plugin_routes(app, abs_path):
    name = os.path.basename(abs_path)
    views = [item if '.' not in item else item.split('.', 1)[0] for item in os.listdir(abs_path) if item.startswith('view_')]
    viewMap = {}
    for view_name in views:
        module = import_module(f'plugins.{name}.{view_name}')
        routes = getattr(module, 'routes')
        if routes:
            viewMap[view_name] = []
            for item in routes:
                # 只打印RouteDef,避免StaticDef报错
                if isinstance(item, web_routedef.RouteDef):
                    viewMap[view_name].append({'method': item.method, 'path': item.path, 'fun_name': item.handler.__name__})
            app.add_routes(routes)
    if app.get('mapping', None) is None:
        app['mapping'] = {name: {}}
    app['mapping'][name] = viewMap

    static_path = os.path.join(abs_path, 'static')
    if os.path.exists(static_path):
        app.router.add_static(f'/static/{name}', path=static_path, name=f'static_{name}')


def load_plugin_middlewares(app, abs_path):
    name = os.path.basename(abs_path)
    middlewares_p = os.path.join(abs_path, 'middlewares.py')
    if os.path.exists(middlewares_p):
        middlewares_p = import_module(f'plugins.{name}.middlewares')
        add_middlewares(app, middlewares_p,name )


def load_db(module_name, depend="CWD"):
    if not os.path.exists(f'{module_name.replace(".", "/")}.py'):
        return
    module = import_module(module_name)
    modules = [getattr(module, item) for item in dir(module) if db.istableclass(getattr(module, item))]
    for item in modules:
        item.module_ = depend
        db.dbs[item.__tablename__] = item

def set_module(module, name, text):
    module = getattr(module, name)
    module.module_ = text
    return module


def load_plugins(app):
    filters_ = {item: set_module(filters, item, 'INNER') for item in dir(
        filters) if isinstance(getattr(filters, item), types.FunctionType)}
    filters_local_ = {}
    if os.path.exists('filters.py'):
        filters_local = import_module('filters')
        filters_local_ = {item: set_module(filters_local, item, 'CWD') for item in dir(
            filters_local) if isinstance(getattr(filters_local, item), types.FunctionType)}
    filters_ = dict(filters_, **filters_local_)
    template_paths = ['templates']
    load_db('db')
    if os.path.exists('plugins'):
        for name in os.listdir('plugins'):
            if name.startswith('_'):
                continue
            abs_path = os.path.abspath(os.path.join('plugins', name))
            if not os.path.isdir(abs_path):
                continue
            template_path, filters_plugin = load_filter_template(abs_path)
            template_paths += template_path
            filters_ = dict(filters_, **filters_plugin)
            load_plugin_routes(app, abs_path)
            load_plugin_middlewares(app, abs_path)
            load_db(f'plugins.{name}.db', name)
    loader = jinja2.FileSystemLoader(template_paths)
    env = jinja2.Environment(loader=loader)
    env.filters = dict(env.filters, **filters_)
    app['__templating__'] = env
    aiohttp_jinja2.setup(app, loader=loader, filters=filters_)

def printResource(app):
    mapping = app.get('mapping', {})
    for plugin_name, views in mapping.items():
        logging.info('{0}{1:^30}{0}'.format('-' * 15, f'plugin [{plugin_name}] mapping'))
        for view_name, items in views.items():
            logging.info('{0}{1:^30}{0}'.format('-' * 15, view_name))
            for item in items:
                logging.info('%-4s %-25s -> %s' % (item['method'], item['path'], item['fun_name']))
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'load middlewares'))
    for item in app.middlewares:
        logging.info('%-31s -> %s' % (item.module_, item.__name__))
    filters = {key: item for key, item in app['__templating__'].filters.items() if getattr(item, 'module_', None) is not None}
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'extra filter'))
    for key, val in filters.items():
        logging.info('%-31s -> %s' % (val.module_, key))
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'database'))
    for key, item in db.dbs.items():
        logging.info('%-31s -> %s' % (item.module_, key))
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'named resources'))
    for name, resource in app.router.named_resources().items():
        logging.info('%-4s %-25s -> %s' % ('GET', resource.canonical, name))
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'load background_tasks'))
    for item in app.cleanup_ctx:
        logging.info('%-31s -> %s' % ('CWD', item.__name__))


async def app_factory_plugin(config={}):
    set_logger()
    app = web.Application(client_max_size=config.get('client_max_size', 104857600))
    app.__config__ = config
    setup_middlewares(app)
    load_plugins(app)
    add_routes(app)
    add_static_routes(app)
    background_tasks(app)
    cors(app)
    printResource(app)
    return app


def setting(config={}):
    settings.config_init()
    settings.set_config(config)
    return settings.config


async def using_db(sql=None, isjson=False):
    set_logger()
    load_db('db')
    if os.path.exists('plugins'):
        for name in os.listdir('plugins'):
            if name.startswith('_'):
                continue
            load_db(f'plugins.{name}.db', name)
    logging.info('{0}{1:^30}{0}'.format('-' * 15, 'database'))
    for key, item in db.dbs.items():
        logging.info('%-31s -> %s' % (item.module_, key))
    result = corelib.execute_sql(sql)
    if isjson:
        import json
        result = json.dumps(result, indent=4)
    print(result)


async def _test_run(runable):
    if asyncio.iscoroutine(runable):
        result = await runable  # type: ignore
        logging.info(result)


def test_run(runable=[]):
    loop = asyncio.get_event_loop()
    result = None
    try:
        loop.run_until_complete(_test_run(runable))
    except (web_runner.GracefulExit, KeyboardInterrupt):  # pragma: no cover
        pass
    finally:
        web._cancel_all_tasks(loop)
        if sys.version_info >= (3, 6):  # don't use PY_36 to pass mypy
            loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()
    logging.info('test run. server does not run')
    exit(0)


def run():
    import argparse
    parser = argparse.ArgumentParser(
        prog='aioweb', description="an async web server devlop by aiohttp")
    parser.add_argument("--host", help="Provide server host")
    parser.add_argument("--port", help="Provide pserver port", type=int)
    parser.add_argument("--configs", help="print config",
                        action="store_true", dest="is_configs", default=False)
    parser.add_argument("--test", help="test run",
                        action="store_true", dest="is_test", default=False)
    parser.add_argument("--json", help="json format result",
                        action="store_true", dest="is_json", default=False)
    parser.add_argument("--sql", help="Provide sql to execute with test run")
    parser.add_argument("--client_max_size",
                        help="Provide client_max_size to request")
    args = parser.parse_args()
    argv = {item[0]: item[1]
            for item in args._get_kwargs() if item[1] is not None}
    config = setting(argv)

    if args.is_configs:
        import json
        result = config
        if args.is_json:
            result = json.dumps(config, indent=4)
        print(result)
    if args.is_test:
        test_run(app_factory_plugin(config))
    if args.sql is not None:
        test_run(using_db(args.sql, args.is_json))
    web.run_app(app_factory_plugin(config), host=config.get(
        'host', '127.0.0.1'), port=config.get('port', 8080))


if __name__ == '__main__':
    import warnings
    warnings.warn(
        "use 'python -m aioweb', not 'python -m aioweb.app'", DeprecationWarning)
    run()
